#pragma once
#include "oj_model2.hpp"
#include "oj_view.hpp"
#include <mutex>
#include <jsoncpp/json/json.h>

#include "../comm/httplib.h"
#include "session.hpp"
#include "../comm/util.hpp"
#include "../comm/Userctl.hpp"

namespace ns_ctrl
{
    using namespace ns_model;
    using namespace ns_view;

    class Machine
    {
    public:
        Machine()
            :_ip("")
            ,_port(0)
            ,_load(0)
            ,_mtx(nullptr)
        {}

        ~Machine()
        {}

        // 提升主机负载
        void AddLoad()
        {
            LockGuardCpp lock(_mtx);
            ++_load;
        }

        // 减少主机负载
        void DelLoad()
        {
            LockGuardCpp lock(_mtx);
            --_load;
        }

        void ClearLoad()
        {
            LockGuardCpp lock(_mtx);

            _load = 0;
        }

        uint64_t GetLoad()
        {
            LockGuardCpp lock(_mtx);
            return _load;
        }

        std::string _ip; //ip地址
        uint16_t _port; //端口 
        uint64_t _load; // 负载
        std::mutex* _mtx; //mutex禁止拷贝，使用指针
    };

    const std::string machine_conf_path = "./conf/server_machine.conf";
    // 负载均衡
    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadConf(machine_conf_path));
            LOG(INFO, "加载配置文件 %s 成功\n", machine_conf_path.c_str());
        }
        ~LoadBalance()
        {}

        // 通过 server_machine.conf文件加载机器列表
        bool LoadConf(const std::string& machine_list)
        {
            std::string machine_conf;
            if(!FileUtil::ReadFile(machine_list, &machine_conf, true))
            {
                LOG(FATAL, "加载编译机器配置文件失败\n");
                return false;
            }

            std::vector<std::string> lines;
            StringUtil::SplitString(machine_conf, "\n", &lines);

            for(auto line : lines)
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, ":", &tokens);

                if(tokens.size() != 2)
                {
                    LOG(ERROR, "加载编译机器，切分 %s 发生错误\n", line.c_str());
                    continue;
                }

                Machine m;
                m._ip = tokens[0];
                m._port = std::stoi(tokens[1]);
                m._load = 0;
                m._mtx = new std::mutex;

                LOG(INFO, "主机上线: %s:%d\n", tokens[0].c_str(), m._port);
                _online.push_back(_machines.size());
                _machines.push_back(m);
            }

            // // Debug
            // int i = 0;
            // for(int e : _online)
            // {
            //     LOG(INFO, "%d -> %s:%d\n", i, _machines[e]._ip.c_str(), _machines[e]._port);
            //     ++i;
            // }
            return true;
        }
        bool SmartChoice(int*id, Machine** mc)
        {
            LockGuardCpp lock(&_mtx);
            if(_online.size() == 0)
            {
                LOG(FATAL, "所有机器都停止运转，机器出现了大故障\n");
                return false;
            }

            *id = _online[0];
            uint64_t min_load = _machines[*id].GetLoad();
            // LOG(INFO, "id: %d, min_load: %d\n", *id, min_load);
            for(int i = 1; i < _online.size(); ++i)
            {
                uint64_t temp_load = _machines[_online[i]].GetLoad();
                if(min_load > temp_load)
                {
                    // 得到最小负载机器的 id
                    *id = _online[i];
                    min_load = temp_load;
                }
            }
            *mc = &_machines[*id];
            LOG(INFO, "选择主机成功，主机为 %d -> %s:%d, 当前负载为 %d, %d ...\n"
            , *id, (*mc)->_ip.c_str(), (*mc)->_port, (*mc)->_load, (*mc)->GetLoad());

            return true;
        }

        void OfflineMachine(int which)
        {
            LockGuardCpp lock(&_mtx);

            for(int i = 0; i < _online.size(); ++i)
            {
                if(_online[i] == which)
                {
                    _machines[which].ClearLoad();
                    _online.erase(_online.begin() + i);
                    _offline.push_back(which);
                    break;
                }
            }
        }

        void OnlineMachine()
        {
            LockGuardCpp lock(&_mtx);
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
        }

        void Print()
        {
            LockGuardCpp lock(&_mtx);

            std::cout << "当前的在线主机列表: ";
            for(int e : _online)
            {
                std::cout << e << " ";
            }
            std::cout << std::endl;

            std::cout << "当前的离线主机列表: ";
            for(int e : _offline)
            {
                std::cout << e << " ";
            }
            std::cout << std::endl;
        }
    private:
        std::vector<Machine> _machines; // 所有主机，下标代表主机id
        std::vector<int> _online; // 在线的主机 id
        std::vector<int> _offline; // 离线的主机 id
        std::mutex _mtx; // 锁
    };
    using namespace httplib;
    class Control
    {
        const std::string mysql_host = "101.34.254.139";
        const std::string mysql_user = "lw";
        const std::string mysql_passwd = "Liwei200408#";
        const std::string mysql_db = "oj";
    uint16_t Mysql_port = 3306;
    public:
        Control()
            :_uc(mysql_host, mysql_user, mysql_passwd, mysql_db)
        {}
        ~Control()
        {}

        void RecoveryMachine()
        {
            _load_balance.OnlineMachine();
            LOG(INFO, "所有主机都恢复了\n");
        }

        // Cookie: SSID=1; path=/;
        bool get_cookie_val(const std::string &cookie_str, const std::string &k, std::string &v)
        {
            std::vector<std::string> single_cookies;
            StringUtil::split(cookie_str, "; ", single_cookies);

            for (auto e : single_cookies)
            {
                std::vector<std::string> line;
                StringUtil::split(e, "=", line);
                if (line.size() != 2)
                    continue;
                if (line[0] == k)
                {
                    v = line[1];
                    return true;
                }
            }
            return false;
        }

        session_ptr GetSession(const Request &req)
        {
            // 1、获取cookie
            std::string cookie_str = req.get_header_value("Cookie");
            if (cookie_str.empty())
            {
                LOG(ERROR, "获取cookie失败, 请重新登录\n");
                return session_ptr(nullptr);
            }

            std::string ssid_s;
            bool ret = get_cookie_val(cookie_str, "SSID", ssid_s);
            if (!ret)
            {
                LOG(ERROR, "获取ssid失败, 请重新登录\n");
                return session_ptr(nullptr);
            }

            int ssid = std::stoi(ssid_s);

            session_ptr sp = _sm.get_session_by_ssid(ssid);
            if (sp.get() == nullptr)
            {
                LOG(ERROR, "获取session失败, 请重新登录\n");
                return session_ptr(nullptr);
            }

            return sp;
        }

        bool AllQuestions(std::string* html, const Request &req)
        {
            session_ptr sp = GetSession(req);
            std::string name;
            if(sp.get() != nullptr)
            {
                int uid = sp->get_user();
                Json::Value user_info;
                _uc.SelectUserById(user_info, uid);
                name = user_info["username"].asString();
            }

            std::vector<Question> all;
            if(_model.GetAllQuestion(&all))
            {
                _view.AllExpandHtml(all, html, name);
            }
            else
            {
                *html = "获取题目列表失败";
                return false;
            }
            return true;
        }

        bool NumQuestion(const std::string& number, std::string* html, const Request &req)
        {
            session_ptr sp = GetSession(req);
            std::string name;
            if(sp.get() != nullptr)
            {
                int uid = sp->get_user();
                Json::Value user_info;
                _uc.SelectUserById(user_info, uid);
                name = user_info["username"].asString();
            }

            Question q;
            if(_model.GetOneQuestion(number, &q))
            {
                _view.OneExpandHtml(q, html, name);
            }
            else
            {
                *html = "获取题目" + number + "失败!";
                return false;
            }
            return true;
        }

        // id: 1
        // code: #include ...
        // input: ...
        void Judge(std::string number, const std::string& in_json, std::string* out_json)
        {
            // 1、根据题号拿到题目细节
            Question q;
            _model.GetOneQuestion(number, &q);
            // 2、通过 in_json 拿到用户提交的代码
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();
            // 3、拼接，形成新的代码
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q._tail;
            compile_value["cpu_limit"] = q._cpu_limit;
            compile_value["mem_limit"] = q._mem_limit;

            Json::StyledWriter writer;
            std::string compile_string = writer.write(compile_value);
            
            while(true)
            {
                // 4、通过负载均衡器选择编译机器
                int id;
                Machine* mc;
                if(!_load_balance.SmartChoice(&id, &mc))
                {
                    // 所有主机都挂了
                    break;
                }
                // 5、发起 http 请求 
                mc->AddLoad();
                httplib::Client cli(mc->_ip, mc->_port);
                httplib::Result res = cli.Post("/CompileAndRun", compile_string, "application/json; charset=utf-8");
                
                // LOG(INFO, "正在请求主机%s:%d\n", mc->_ip.c_str(), mc->_port);
                // 6、拿到结果返回
                if(res.error() == 0)
                {
                    *out_json = res->body;
                    // ***************************** 为了测试注释的DelLoad
                    // mc->DelLoad();
                    LOG(INFO, "向主机%s:%d, 请求编译与运行服务成功...\n", mc->_ip.c_str(), mc->_port);
                    // LOG(INFO, "请求编译与运行服务成功...\n");
                    break;
                }
                else
                {
                    LOG(ERROR, "当前请求的主机id: %d，已离线\n", id);
                    _load_balance.OfflineMachine(id);
                    _load_balance.Print();
                    continue;
                }
            }
        }

        void http_resp(bool result, const std::string& reason, Response &resp, int code)
        {
            Json::Value req;
            req["result"] = result;
            req["reason"] = reason;

            std::string json_str;
            json_util::serialize(req, json_str);

            resp.set_content(json_str, "application/json; charset=utf-8");
            resp.status = code;
        }

        // 注册请求
        void Reg(std::string body, Response &resp)
        {
            Json::Value login_info;
            bool ret = json_util::deserialize(login_info, body);
            if (!ret)
            {
                LOG(ERROR, "注册信息反序列化失败\n");
                return http_resp(false, "请求正文格式错误失败", resp, 400);
            }

            if (login_info["username"].isNull() || login_info["password"].isNull())
            {
                LOG(ERROR, "注册用户名密码不完整\n");
                return http_resp(false, "请输入用户名和密码", resp, 400);
            }

            ret = _uc.InsertUser(login_info);
            if (!ret)
            {
                LOG(ERROR, "注册用户名重复\n");
                return http_resp(false, "用户名已经被占用!", resp, 400);
            }

            return http_resp(true,"注册用户成功!", resp, 200);
        }

        bool RegHtml(std::string* html)
        {
            return _view.GetRegHtml(html);
        }

        bool LoginHtml(std::string* html)
        {
            return _view.GetLoginHtml(html);
        }

        bool AlreadyLoginHtml(std::string* html, const Request& req)
        {
            session_ptr sp = GetSession(req);
            std::string name;
            if(sp.get() == nullptr)
                return false;

            int uid = sp->get_user();
            Json::Value user_info;
            _uc.SelectUserById(user_info, uid);
            name = user_info["username"].asString();

            _view.GetAlreadyLoginHtml(html, name);

            return true;
        }

        bool IndexHtml(std::string* html, const Request& req)
        {
            // 登录了，就有登录后的主页，没登录，就去没登录的主页
            session_ptr sp = GetSession(req);
            if(sp.get() != nullptr)
            {
                LOG(INFO, "already...\n");
                // return _view.GetAlreadyLoginHtml(html);
            }
            LOG(INFO, "unlogin...\n");
            return _view.GetindexHtml(html);
        }

        // 登录请求
        // body 为请求 json 串
        void login(std::string body, Response& resp)
        {
            Json::Value login_info;
            bool ret = json_util::deserialize(login_info, body);
            // std::cout << "login_info :" << login_info << std::endl;

            if (!ret)
            {
                LOG(ERROR, "登录信息反序列化失败\n");
                return http_resp(false, "请求正文格式错误失败", resp, 400);
            }

            std::string username = login_info["username"].asString();
            std::string password = login_info["password"].asString();
            if (username.empty() || password.empty())
            {
                LOG(ERROR, "登录用户名密码不完整\n");
                return http_resp(false, "请输入用户名和密码", resp, 400);
            }

            if (!_uc.Login(login_info))
            {
                LOG(ERROR, "登录用户名或密码错误\n");
                return http_resp(false, "用户名或密码错误", resp, 400);
            }

            // json中没有uid
            Json::Value user_info;
            std::string user_info_str;
            
            _uc.SelectUserByName(user_info, username);
            if (!json_util::serialize(user_info, user_info_str))
            {
                LOG(INFO, "json_util::serialize(user_info, user_info_str) fail\n");
            }

            // LOG(INFO, "user_info_str: %s\n", user_info_str.c_str());
            // 创建会话
            int uid = user_info["id"].asInt();
            session_ptr sp = _sm.create_session(uid, LOGIN);
            if (sp.get() == nullptr)
            {
                LOG(ERROR, "登录创建会话失败\n");
                return http_resp(false, "创建会话失败", resp, 400);
            }
            // 设置cookie
            std::string cookie_ssid = "SSID=" + std::to_string(sp->GetSsid());
            resp.set_header("Set-Cookie", cookie_ssid);

            LOG(INFO, "用户登录成功!\n");
            return http_resp(true, "用户登录成功!", resp, 200);
        }

    private:
        Model _model; // 提供后台数据
        View _view;   // 提供 html 网页渲染功能
        LoadBalance _load_balance; // 负载均衡器

        UserCtl _uc;
        SessionManage _sm;
    };
}